/*
 *  $Id$
 *  Copyright [2010] [Panxiaobo] 
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package pxb.openid.rp;

import static pxb.openid.util.Util.checkNotNull;

import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pxb.openid.Association;
import pxb.openid.Constants;
import pxb.openid.OpenIdException;
import pxb.openid.util.Base64;
import pxb.openid.util.Mac;

/**
 * 验证OP传回的数据
 * 
 * @author Panxiaobo
 * 
 */
public class Verifier implements Constants {
    private static final Logger log = LoggerFactory.getLogger(Verifier.class);
    private static final Charset UTF8 = Charset.forName("UTF-8");

    public Map<String, String> verify(Association context, Map<String, String> args) throws Exception {

        checkNotNull("Context Or args is null", context, args);

        if (log.isDebugEnabled()) {
            log.debug("OP->RP auth response params:");
            log.debug("==================== Start");
            Map<String, String> map = new TreeMap<String, String>(args);
            for (Map.Entry<String, String> entry : map.entrySet()) {
                log.debug("{} = {}", entry.getKey(), entry.getValue());
            }
            log.debug("==================== End");
        }

        String mode = args.get(OPENID_MODE);
        if (null == mode || "cancel".equals(mode))
            throw new OpenIdException("Cancled by user");

        if ("error".equals(mode)) {
            throw new OpenIdException("Error in auth");
        }

        byte[] macKey = context.getMac();
        checkNotNull("no MacKey in context", macKey);

        String signature = args.get(OPENID_SIG);
        checkNotNull("signature is null", signature);
        log.debug("signature:{}", signature);

        String sigList = args.get(OPENID_SIGNED);
        if (!(sigList.contains("op_endpoint") && sigList.contains("return_to") && sigList.contains("response_nonce")
                && sigList.contains("assoc_handle") && sigList.contains("claimed_id") && sigList.contains("identity"))) {
            throw new OpenIdException(
                    "op_endpoint,return_to,response_nonce,assoc_handle,claimed_id,identity must sign by provider");
        }
        Map<String, String> trusted = new HashMap<String, String>();
        StringTokenizer st = new StringTokenizer(sigList, ",");
        StringBuffer sb = new StringBuffer();
        while (st.hasMoreTokens()) {
            String key = st.nextToken();
            String oKey = "openid." + key;
            String value = (String) args.get(oKey);
            if (value == null) {
                throw new OpenIdException("Cannot sign non-existent mapping: " + key);
            }
            trusted.put(oKey, value);
            sb.append(key).append(':').append(value).append('\n');
        }
        String algorithm = context.getType();
        log.debug("association type:{}", algorithm);

        byte[] b;
        if (algorithm == null || algorithm.equals(Constants.HMAC_SHA1)) {
            b = Mac.hmacSha1(macKey, sb.toString().getBytes(UTF8));
        } else {
            b = Mac.hmacSha256(macKey, sb.toString().getBytes(UTF8));
        }

        String reSignature = new String(Base64.encodeBase64(b));

        log.debug("reSignature:{}", reSignature);
        boolean result = reSignature.equals(signature);
        if (!result) {
            log.warn("signature mismatch.");
            throw new RuntimeException("signature mismatch");
        }
        return trusted;
    }
}
